package com.archgeek.scala.code


import org.apache.commons.lang3.SystemUtils
import org.apache.commons.lang3.time.{DateFormatUtils, FastDateFormat}

import java.io._
import java.net.URL
import java.nio.charset.StandardCharsets.UTF_8
import java.nio.file.{Files, Path, Paths}
import java.util.{Date, Properties, Random, TimeZone, UUID}
import scala.annotation.tailrec
import scala.collection.JavaConverters._
import scala.concurrent.TimeoutException
import scala.concurrent.duration.Duration
import scala.util.control.NonFatal

/**
 *
 *
 * Create by pizhihui on 2023-02-16
 */
/*
*@auther: livy
*@date: 2018/4/11 :: 14:13
*@param:
*@history:ravizhang@lexinfintech.com
*@param:
*/
object Utils{

  private val random: Random = new Random()

  def loadProperties(url: URL): Map[String, String] = {
    val inReader = new InputStreamReader(url.openStream(), UTF_8)
    try {
      val properties = new Properties()
      properties.load(inReader)
      properties.stringPropertyNames().asScala.map { k =>
        (k, properties.getProperty(k).trim())
      }.toMap
    } finally {
      inReader.close()
    }
  }


  def getPropertiesFromPath(filePath: String): Map[String, String] = {
    val fins = this.getClass.getResourceAsStream(filePath);
    try {
      val properties = new Properties()
      properties.load(fins)
      properties.stringPropertyNames().asScala.map { k =>
        (k, properties.getProperty(k).trim())
      }.toMap
    } finally {
      fins.close()
    }
  }

  /**
   * Checks if event has occurred during some time period. This performs an exponential backoff
   * to limit the poll calls.
   *
   * @param checkForEvent
   * @param atMost
   * @throws java.util.concurrent.TimeoutException
   * @throws java.lang.InterruptedException
   * @return
   */
  @throws(classOf[TimeoutException])
  @throws(classOf[InterruptedException])
  final def waitUntil(checkForEvent: () => Boolean, atMost: Duration): Unit = {
    val endTime = System.currentTimeMillis() + atMost.toMillis

    @tailrec
    def aux(count: Int): Unit = {
      if (!checkForEvent()) {
        val now = System.currentTimeMillis()

        if (now < endTime) {
          val sleepTime = Math.max(10 * (2 << (count - 1)), 1000)
          Thread.sleep(sleepTime)
          aux(count + 1)
        } else {
          throw new TimeoutException
        }
      }
    }

    aux(1)
  }

  /** Returns if the process is still running */
  def isProcessAlive(process: Process): Boolean = {
    try {
      process.exitValue()
      false
    } catch {
      case _: IllegalThreadStateException =>
        true
    }
  }

  def startDaemonThread(name: String)(f: => Unit): Thread = {
    val thread = new Thread(name) {
      override def run(): Unit = f
    }
    thread.setDaemon(true)
    thread.start()
    thread
  }

  def usingResource[A <: Closeable, B](resource: A)(f: A => B): B = {
    try {
      f(resource)
    } finally {
      resource.close()
    }
  }

  def hostName(): String = {
    System.getenv("HOSTNAME")
  }

  def isLocal(): Boolean = {
    // 这里简单判断
    SystemUtils.IS_OS_MAC || SystemUtils.IS_OS_WINDOWS
  }

  /**
   * Create a temporary directory inside the given parent directory. The directory will be
   * automatically deleted when the VM shuts down.
   */
  def createTempDir(
                     root: String = System.getProperty("java.io.tmpdir"),
                     namePrefix: String = "kyuubi"): Path = {
    val dir = createDirectory(root, namePrefix)
    dir.toFile.deleteOnExit()
    dir
  }

  private val MAX_DIR_CREATION_ATTEMPTS: Int = 10

  /**
   * Create a directory inside the given parent directory. The directory is guaranteed to be
   * newly created, and is not marked for automatic deletion.
   */
  def createDirectory(root: String, namePrefix: String = "kyuubi"): Path = {
    var error: Exception = null
    (0 until MAX_DIR_CREATION_ATTEMPTS).foreach { _ =>
      val candidate = Paths.get(root, s"$namePrefix-${UUID.randomUUID()}")
      try {
        val path = Files.createDirectories(candidate)
        return path
      } catch {
        case e: IOException => error = e
      }
    }
    throw new IOException(
      "Failed to create a temp directory (under " + root + ") after " + MAX_DIR_CREATION_ATTEMPTS +
        " attempts!",
      error)
  }



  /**
   * return date of format yyyyMMdd
   */
  def getDateFromTimestamp(time: Long): String = {
    DateFormatUtils.format(time, "yyyyMMdd", TimeZone.getDefault)
  }

  def getDefaultTimeStr(): String = {
    "1970-01-01 00:00:00"
  }

  /**
   * 当前时间的str
   * @return 2023-02-26 12:00:01
   */
  def getNowStr(): String = {
    FastDateFormat.getInstance("yyyy-MM-dd HH:mm:ss").format(new Date())
  }

  def getTimeStr(): String = {
    FastDateFormat.getInstance("yyyyMMddHHmmss").format(new Date())
  }

  def getPathSuffix(pathStr: String): String = {
    val pth = Paths.get(pathStr);
    val count = pth.getNameCount
    val subPath = pth.subpath(count-1, count)
    subPath.toString
  }


  /**
   * Make a string representation of the exception.
   */
  def stringifyException(e: Throwable): String = {
    val stm = new StringWriter
    val wrt = new PrintWriter(stm)
    e.printStackTrace(wrt)
    wrt.close()
    stm.toString
  }


  /**
   * 有异常就重试到最大的 maxRetries 次数
   * 没有异常,就执行一次完成
   *
   * 参考资料: https://softwaremill.com/retrying-function-calls-in-scala/
   *
   * @param maxRetries 最大重试次数
   * @param n 默认值,不传
   * @param fn 需要执行的方法
   * @return fn
   */
  @tailrec
  def retry[T](maxRetries: Int, n: Int = 0)(fn: Int => T): T = {
    try {
      fn(n)
    } catch {
      case e: Exception =>
        if (n < maxRetries - 1) retry(maxRetries, n + 1)(fn)
        else throw e
    }
  }


  // include
  // exclude
  def randomNum(startInc: Int, endExc: Int): Int = {
    random.nextInt(endExc) + (endExc - startInc)
  }


}
